场景:评论,点赞等场景数据比较多,访问量大,用数据库或者ES做分页比较浪费而且负载会很大,所以这种场景比较适合用redis的有序集合做分页。
问题:Redis有序集合排序分页,当数据出现增加或删除的时候,索引会变动,会影响分页加载的结果。
举例:用户点击查看评论,原始评论按照评论id正序排序数据为 1,2,3,4,5,6,7,8,9.... N,假设每页显示4条数据,page1的数据页为1,2,3,4;
如果用户在翻看第二页之前,评论3,4对应的作者删除了评论,redis有序集合数据变成了1,2,5,6,7,8,9....
N,此时用户查看第二页,服务端执行redis分页查询(zrange comment_key 4 7), 则取到的结果为 7,8,9,10 ;
此时评论5,6被跳过了。
解决方案:用户请求下一页的时候,客户端把之前一页返回的最大值回传给服务端,以上示例请求第二页的时候,把第一页的最大值4传给服务端,服务端接受到第二页的请求之后,再次查询第一页的数据,如果查询到的最大值小于等于4,则直接取第二页数据返回给客户端,如果第一页最大值大于4,则用第二大的值和4对比,如果还是大于4,游标继续向前,直到找到小于等于4的停下来,然后从此处索引+1作为起始位置取分页数据。以上面的示例演示流程:
- 客户端请求某个帖子的第二页评论数据,传给服务端pagesize=2, lastscore=4
- 服务端先取前一页数据,zrange comment_key 0,3 withscores 得到最新结果 1,2,5,6
- 对比最大值 6 和 4,发现6比4大,前移,5还是大于4,继续前移,直到2<5,停下来
- 获取 2 节点value对应的索引位置为 1(起始索引位置为0),
- 获取第二页数据,zrange comment_key 2,5 withscores 得到 5,6,7,8 返回给客户端
备注:
- 复杂度分析,翻一页就能校正分页游标的复杂度为 O(log(N)+M),N为集合长度,M为每页数据条数,极限情况的负责度为 O((log(N)+M)* N/M) ,就是需要向前翻所有页才能校正,假设取最后一页数据之前,前面的评论全部被删了,呵呵呵
- 如果是倒叙排列,数据新增会改变排序结果,上述方案同样适用,只是把向前翻页校正游标改为向后翻页校正游标,校正后,索引+1变为索引-1
- 有的同学为了解决这类问题会用数据快照缓存的方式,即用户查看某个帖子评论的时候,一次性的为这个用户加载10页数据或者更多,然后翻页过程中从数据快照中直接取比较快,此时数据变化用户无感知,但是这样做的坏处也很多,数据更新不及时,被删除的评论用户还能加载到;要为每个用户都要预存一份快照数据,浪费大量内存空间
如果大牛们有更好的方案,欢迎指教,不胜受恩感激。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。